package abacab.remote.netty;

import java.net.InetSocketAddress;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

import abacab.remote.Connection;
import abacab.remote.ConnectionRegistry;

/**
 * Represents a connection on server side
 */
@SuppressWarnings("synthetic-access")
public final class NettyServerConnection extends Connection {
	/** The handler for the channel */
	private final ChannelHandler	handler;

	/**
	 * Create the handler
	 * 
	 * @param registry
	 *          the registry
	 */
	public NettyServerConnection(ConnectionRegistry registry) {
		super(registry);
		this.handler = new ChannelHandler();
	}

	/**
	 * @return the handler
	 */
	public final ChannelHandler getHandler() {
		return this.handler;
	}

	@Override
	public String getRemoteHostPort() {
		return this.handler.hostPort;
	}

	@Override
	public InetSocketAddress getRemoteAddress() {
		return (InetSocketAddress) this.handler.channel.getRemoteAddress();
	}

	@Override
	public void close() {
		this.handler.close();
	}

	@Override
	protected void send(byte[] message) {
		this.handler.send(message);
	}

	/**
	 * The channel handler
	 */
	private final class ChannelHandler extends SimpleChannelHandler {
		/** The channel */
		private volatile Channel	channel;

		/** The host & port */
		private volatile String		hostPort;

		/**
		 * Close the connection
		 */
		void close() {
			try {
				unregister();
				this.channel.close();
			}
			catch (NullPointerException e) {
				// Not yet connected ;o)
			}
		}

		@Override
		public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
			this.channel = ctx.getChannel();
			InetSocketAddress address = (InetSocketAddress) this.channel.getRemoteAddress();
			this.hostPort = address.getHostName() + ":" + address.getPort();
			register();
			super.channelConnected(ctx, e);
		}

		@Override
		public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
			unregister();
			super.channelDisconnected(ctx, e);
		}

		/**
		 * Send a message
		 * 
		 * @param message
		 *          the message
		 */
		protected void send(byte[] message) {
			ChannelBuffer buffer = ChannelBuffers.buffer(message.length + 2);
			buffer.writeShort(message.length);
			buffer.writeBytes(message);
			this.channel.write(buffer);
		}

		@Override
		public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
			NettyServerConnection.this.messageReceived(((ChannelBuffer) e.getMessage()).array());
		}

		@Override
		public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
			e.getCause().printStackTrace();
			e.getChannel().close();
		}
	}
}